<?php
class TemplateCompiler{
	
	/**
	 * @var TemplateEngine
	 */
	private $engine;
	
	/**
	 * The template itself
	 *
	 * @var string
	 */
	private $template;
	
	/**
	 * The template filename used to extract the dirname for subtemplates
	 *
	 * @var string
	 */
	private $template_dir;
	
	private $template_file;
	
	/**
	 * List of used TemplateEngine Extensions
	 *
	 * @var array
	 */
	private $extension_tagged = array();
	
	/**
	 * Error messages
	 *
	 * @var string
	 */
	public $error;
	
	private static $_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; // regexps are setup in the constructor
	private static $_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
	private static $_func_regexp = '[a-zA-Z_\/]+';
	private static $_var_bracket_regexp = '\[[\$|\#]?\w+\#?\]';
	private static $_svar_regexp = '\%\w+\.\w+\%';
	private $_qstr_regexp = null;
	private $_dvar_regexp = null;
	private $_cvar_regexp = null;
	private $_var_regexp = null;
	
	private $_foreach_stack = array();
	private $_for_stack = 0;
	private $_switch_stack = array();
	
	/**
	 * TemplateCompiler Constructor
	 *
	 * @param TemplateEngine engine
	 * @param string $template_filename HTML Template Filename
	 */
	function __construct($engine,$template_filename){
		$this->engine = &$engine;
		$this->_qstr_regexp = '(?:' . self::$_db_qstr_regexp . '|' . self::$_si_qstr_regexp . ')';
		$this->_dvar_regexp = '\$[a-zA-Z0-9_]{1,}(?:' . self::$_var_bracket_regexp . ')*(?:\.\$?\w+(?:' . self::$_var_bracket_regexp . ')*)*';
		$this->_cvar_regexp = '\#[a-zA-Z0-9_]{1,}(?:' . self::$_var_bracket_regexp . ')*(?:' . self::$_var_bracket_regexp . ')*\#';
		$this->_var_regexp = '(?:(?:' . $this->_dvar_regexp . '|' . $this->_cvar_regexp . ')|' . $this->_qstr_regexp . ')';
		
		// Load Template
		if(($this->template = file_get_contents($template_filename))){
			$this->template_dir = dirname($template_filename);
			$this->template_file = basename($template_filename);
		}else{
			trigger_error("Cmspad Template Compiler Error: File not found or empty: '$template_filename'",E_USER_ERROR);
		}
	}
	
	/**
	 * Main Template Parser
	 *
	 * @param string $compiled_template_filename Compiled Template Filename
	 * @desc Creates Compiled PHP Template
	 */
	public function compile($compiled_template_filename = ''){
		if(empty($this->template)){
			return false;
		}
		
		$startTime = $this->stime();
		
		/* Internal functions */
		$page = $this->compileInclude($this->template);
		$page = $this->compileCode($page);
		$page = $this->compileIf($page);
		$page = $this->compileWhile($page);
		
		// System functions
		$page = $this->compileExt($page);
		
		// System variables
		$page = $this->compileExtVariables($page);
		
		// System tagged functions
		$page = $this->compileTag($page);
		
		$endTime = $this->stime() - $startTime;
		
		// Debug information
		$msgs = array();
		$msgs['type'] = 'compile';
		$msgs['file'] = $this->template_file;
		$msgs['directory'] = $this->template_dir;
		$msgs['time'] = $endTime;
		$msgs['functions'] = $this->engine->getFunctions();
		$msgs['variables'] = $this->engine->getVariables();
		TemplateEngine::$_messages[microtime()] = $msgs;
		
		//	Store Code to Temp Dir
		if(strlen($compiled_template_filename)){
			if(($hd = fopen($compiled_template_filename,"w"))){
				fwrite($hd,$page);
				fclose($hd);
				return true;
			}else{
				$this->error = "Could not write compiled file: " . $compiled_template_filename;
				return false;
			}
		}else{
			return $page;
		}
	}
	
	private function compileInclude($page){
		if(strpos($page,"<{include") !== false){
			while($this->countIncludeTags($page) > 0){
				preg_match_all('/<{include\s+["]?([a-zA-Z0-9_.\-,]+)["]?\s*}>/',$page,$tvar);
				foreach($tvar[1] as $cnt=>$subfile){
					$page = str_replace($tvar[0][$cnt],"<?php echo cmspad_template('$subfile', \$_variables);?>",$page);
				}
			}
		}
		return $page;
	}
	
	private function compileCode($page){
		if(strpos($page,'<{php}>') !== false){
			$page = str_replace('<{php}>','<?php ',$page);
			$page = str_replace('<{/php}>','?>',$page);
			if(strnatcmp(PHP_VERSION,'4.3.0') >= 0){
				$php_extract = '<?php extract($_variables, EXTR_REFS);?>';
			}else{
				$php_extract = '<?php extract($_variables);?>';
			}
			$page = $php_extract . $page;
		}
		return $page;
	}
	
	private function compileWhile($page){
		$page = str_replace("<{/while}>","<?php endwhile;?>",$page);
		//	'if $nnn=mmm' Blocks
		if(preg_match_all('/<{while\s+([^!=<>]+)\s*([!=<>]+)\s*(["]?[^"]*?["]?)\s*}>/',$page,$var)){
			foreach($var[1] as $cnt=>$tag){
				$nam = $this->parseValue($tag);
				$cmp = $this->parseComparator($var[2][$cnt]);
				if(!$cmp){
					continue;
				}
				$val = $this->parseCondition($var[3][$cnt]);
				$code = '<?php while(' . $nam . ' ' . $cmp . ' ' . $val . '):?>';
				$page = str_replace($var[0][$cnt],$code,$page);
			}
		}
		return $page;
	}
	
	private function compileIf($page){
		$page = str_replace("<{/if}>","<?php endif;?>",$page);
		$page = str_replace("<{else}>","<?php else:?>",$page);
		
		//	'if $nnn' Blocks
		if(preg_match_all('/<{(else)?if\s+\$([a-zA-Z0-9_.]+)\s*}>/',$page,$var)){
			foreach($var[2] as $cnt=>$tag){
				$else = ($var[1][$cnt] == 'else')?'else':'';
				list($parent,$block) = $this->parseVariable($tag);
				$code = "<?php $else" . "if(!empty(\$$parent" . "['$block'])):?>";
				$page = str_replace($var[0][$cnt],$code,$page);
			}
		}
		
		//	'if @nnn' Blocks
		if(preg_match_all('/<{(else)?if\s+@([a-zA-Z0-9_.]+)\s*}>/',$page,$var)){
			foreach($var[2] as $cnt=>$tag){
				$else = ($var[1][$cnt] == 'else')?'else':'';
				$code = "<?php $else" . "if(function_exists('$tag') || !empty(\$_functions['ext']['$tag'])):?>";
				$page = str_replace($var[0][$cnt],$code,$page);
			}
		}
		
		//	'if $nnn=mmm' Blocks
		if(preg_match_all('/<{(else)?if\s+([^!=<>]+)\s*([!=<>]+)\s*(["]?[^"]*?["]?)\s*}>/',$page,$var)){
			foreach($var[2] as $cnt=>$tag){
				$nam = $this->parseValue($tag,true);
				$cmp = $this->parseComparator($var[3][$cnt]);
				if(!$cmp){
					continue;
				}
				$val = $this->parseCondition($var[4][$cnt]);
				$else = ($var[1][$cnt] == 'else')?'else':'';
				$code = '<?php ' . $else . 'if(' . $nam . ' ' . $cmp . ' ' . $val . '):?>';
				$page = str_replace($var[0][$cnt],$code,$page);
			}
		}
		
		return $page;
	}
	
	private function compileExt($page){
		$var = array(); // stores the tags
		if(preg_match_all('/<{@([a-zA-Z0-9_]+)\s*(.*?)\s*}>/',$page,$var)){
			foreach($var[2] as $cnt=>$args){
				$funcArgs = $this->compileExtArguments($args);
				$funcName = $var[1][$cnt];
				if(substr($funcName,0,1) == '_'){
					trigger_error('Cmspad Template Compiler Warning: Function name(' . $funcName . ') can not start with "_"',E_USER_WARNING);
					continue;
				}
				$expr = $this->compileExtFunctions($funcName,$funcArgs,$page);
				if($expr){
					$page = str_replace($var[0][$cnt],$expr,$page);
				}
			}
		}
		return $this->compileExtVariables($page);
	}
	
	/**
	 * Parse function tags.
	 * <b>Note:</b> It can replace default function if they are the same name.
	 * <p>e.g. Tag <code>&lt;{@strtolower "HELLO"}&gt;</code> will be converted to <code>hello</code></p>
	 *
	 * @param string $page
	 * @return string
	 */
	private function compileExtFunctions($funcName,$funcArgs){
		if(($extFunc = $this->engine->getExtFunction($funcName))){
			if(is_array($extFunc)){
				$funcName = '$_functions[\'ext\'][\'' . $funcName . '\']';
			}else{
				$funcName = '\'' . $extFunc . '\'';
			}
		}else if(!function_exists($funcName)){
			trigger_error('Cmspad Template Compiler Warning: Function name(Ext: ' . $funcName . ') not found in registry',E_USER_WARNING);
			return false;
		}else{
			$funcName = '\'' . $funcName . '\'';
		}
		return $funcArgs?"<?php echo call_user_func($funcName,$funcArgs);?>":"<?php echo call_user_func($funcName);?>";
	}
	
	private function compileExtArguments($args){
		$esc = false;
		$quotation = false;
		$arguments = array();
		for($i = 0,$l = strlen($args);$i < $l;$i++){
			$length = count($arguments);
			$argc = $args[$i];
			if($esc){
				if($length > 0){
					$arguments[$length - 1] .= '\\' . $argc;
				}else{
					$arguments[] = '\\' . $argc;
				}
				$esc = false;
				continue;
			}
			switch($argc){
				case '\\':
					$esc = true;
					break;
				case '"':
					if($quotation){
						$quotation = false;
					}else{
						$quotation = true;
					}
					break;
				case ' ':
					if($quotation){
						if($length > 0){
							$arguments[$length - 1] .= $argc;
						}else{
							$arguments[] = $argc;
						}
					}else{
						$arguments[] = '';
					}
					break;
				default:
					if($length > 0){
						$arguments[$length - 1] .= $argc;
					}else{
						$arguments[] = $argc;
					}
					break;
			}
		}
		// Convert variable to real contents
		$tmpArgs = $arguments;
		$arguments = array();
		foreach($tmpArgs as $tmpArg){
			if($tmpArg){
				$argument = $this->compileSysVariables($tmpArg);
				$arguments[] = $argument;
			}
		}
		return $this->parseValue($arguments);
	}
	
	private function compileExtVariables($page){
		//	Replace Scalars
		if(preg_match_all('/<{\$([a-zA-Z0-9_.]+)}>/',$page,$var)){
			foreach($var[1] as $tag){
				list($block,$skalar) = $this->parseVariable($tag);
				$code = "<?php echo isset(\$$block" . "['$skalar'])?\$$block" . "['$skalar']:'';?>";
				$page = str_replace('<{$' . $tag . '}>',$code,$page);
			}
		}
		
		if(preg_match_all('/<{"(.+)"}>/',$page,$var)){
			foreach($var[1] as $tag){
				$value = $this->parseValue($tag);
				$code = "<?php echo cmspad_get_language($value);?>";
				$page = str_replace('<{"' . $tag . '"}>',$code,$page);
			}
		}
		return $page;
	}
	
	/**
	 * Parse internal functions in given string.
	 * <p>e.g. String <code>hello strtoupper(world)</code> will be converted to <code>hello WORLD</code>.</p>
	 *
	 * @param string $contents
	 * @return string
	 */
	private function compileSysFunctions($contents){
		if(preg_match_all('/([a-zA-Z0-9_]+)\((.*?)\)/',$contents,$var)){
			foreach($var[2] as $cnt=>$args){
				$funcArgs = $this->compileSysArguments($args);
				$funcName = $var[1][$cnt];
				if(($sysFunc = $this->engine->getSysFunction($funcName))){
					if(is_array($sysFunc)){
						$funcName = '$_functions[\'sys\'][\'' . $funcName . '\']';
					}else{
						$funcName = '\'' . $sysFunc . '\'';
					}
				}else{
					if(substr($funcName,0,1) == '_'){
						trigger_error('Cmspad Template Compiler Warning: Function name(' . $funcName . ') can not start with "_"',E_USER_WARNING);
						continue;
					}
				}
				if($funcArgs){
					$contents = str_replace($var[0][$cnt],"'.call_user_func('$funcName',$funcArgs).'",$contents);
				}else{
					$contents = str_replace($var[0][$cnt],"'.call_user_func('$funcName').'",$contents);
				}
			}
		}
		return $this->compileSysVariables($contents);
	}
	
	private function compileSysArguments($args){
		$esc = false;
		$quotation = false;
		$arguments = array();
		for($i = 0,$l = strlen($args);$i < $l;$i++){
			$length = count($arguments);
			$argc = $args[$i];
			if($esc){
				if($length > 0){
					$arguments[$length - 1] .= '\\' . $argc;
				}else{
					$arguments[] = '\\' . $argc;
				}
				$esc = false;
				continue;
			}
			switch($argc){
				case '\\':
					$esc = true;
					break;
				case '\'':
					if($length > 0){
						$arguments[$length - 1] .= $argc;
					}else{
						$arguments[] = $argc;
					}
					if($quotation){
						$quotation = false;
					}else{
						$quotation = true;
					}
					break;
				case ',':
					if($quotation){
						if($length > 0){
							$arguments[$length - 1] .= $argc;
						}else{
							$arguments[] = $argc;
						}
					}else{
						$arguments[] = '';
					}
					break;
				default:
					if($length > 0){
						$arguments[$length - 1] .= $argc;
					}else{
						$arguments[] = $argc;
					}
					$esc = false;
					break;
			}
		}
		
		return $this->parseValue($arguments);
	}
	
	/**
	 * Parse template variables.
	 * <p>e.g. String <code>hello, $message</code> will be converted to <code>hello, template</code> after set template with the following code: <code>$template-&gt;addVariable('message', 'template');</code></p>
	 *
	 * @param string $contents
	 * @return string
	 */
	private function compileSysVariables($contents){
		if(preg_match_all('/\$([a-zA-Z0-9_.]+)/',$contents,$var)){
			$vars = array();
			$keys = array();
			foreach($var[1] as $tag){
				if(substr($tag,0,1) == '_'){
					continue;
				}
				list($block,$skalar) = $this->parseVariable($tag);
				$vars[] = "'.\$$block" . "['$skalar'].'";
				$keys["'.\$$block" . "['$skalar'].'"] = $tag;
			}

			usort($vars,array(&$this,'_variableSortComparator'));
			foreach($vars as $var){
				$key = $keys[$var];
				$var = $this->trimValue($var);
				$contents = str_replace('$' . $key,"(isset($var)?$var:NULL)",$contents);
			}
		}
		return $contents;
	}
	
	public function _variableSortComparator($var1,$var2){
		$l1 = strlen($var1);
		$l2 = strlen($var2);
		if($l1 > $l2){
			return -1;
		}else if($l1 < $l2){
			return 1;
		}else{
			return 0;
		}
	}
	
	/**
	 * @param string $page
	 * @return mix
	 */
	private function compileTag($page){
		$_matches = array(); // stores the tags
		// extract the tag command, modifier and arguments
		if(preg_match_all('/<{(' . self::$_func_regexp . ')(\s+(.*?))?}>/xs',$page,$_matches)){
			foreach($_matches[1] as $_cnt=>$tag_command){
				$tag_arguments = !empty($_matches[2][$_cnt])?$_matches[2][$_cnt]:null;
				$tag_result = $this->compileTagFunctions($tag_command,$tag_arguments);
				$page = str_replace($_matches[0][$_cnt],$tag_result,$page);
			}
		}
		return $page;
	}
	
	/**
	 * @param string $page
	 * @param string $tag
	 * @param array $args
	 */
	private function compileTagFunctions($function,$arguments){
		switch($function){
			case 'foreach':
				array_push($this->_foreach_stack,false);
				$_args = $this->compileTagArguments($arguments);
				if(!isset($_args['from'])){
					trigger_error("Cmspad Template Compiler Error: Missing 'from' attribute in 'foreach'",E_USER_ERROR);
				}
				if(!isset($_args['value']) && !isset($_args['item'])){
					trigger_error("Cmspad Template Compiler Error: Missing 'value' attribute in 'foreach'",E_USER_ERROR);
				}
				if(!isset($_args['value']) && isset($_args['item'])){
					$_args['value'] = $_args['item'];
				}
				isset($_args['key'])?$_args['key'] = "\$_variables[" . $_args['key'] . "] => ":$_args['key'] = '';
				$_args['from'] = trim($_args['from']);
				$_result = '<?php if(count((array)' . $_args['from'] . ')): foreach ((array)' . $_args['from'] . ' as ' . $_args['key'] . '$_variables[' . $_args['value'] . ']):?>';
				return $_result;
			case 'foreachelse':
				$this->_foreach_stack[count($this->_foreach_stack) - 1] = true;
				return "<?php endforeach;else:?>";
			case '/foreach':
				if(array_pop($this->_foreach_stack)){
					return "<?php endif;?>";
				}else{
					return "<?php endforeach;endif;?>";
				}
			case 'for':
				$this->_for_stack++;
				$_args = $this->compileTagArguments($arguments,array('start','stop','step'));
				if(!isset($_args['start'])){
					trigger_error("Cmspad Template Compiler Error: Missing 'start' attribute in 'for'",E_USER_ERROR);
				}
				if(!isset($_args['stop'])){
					trigger_error("Cmspad Template Compiler Error: Missing 'stop' attribute in 'for'",E_USER_ERROR);
				}
				if(!isset($_args['step'])){
					$_args['step'] = 1;
				}
				$_result = '<?php for($for' . $this->_for_stack . ' = ' . $_args['start'] . '; ((' . $_args['start'] . ' < ' . $_args['stop'] . ') ? ($for' . $this->_for_stack . ' < ' . $_args['stop'] . ') : ($for' . $this->_for_stack . ' > ' . $_args['stop'] . ')); $for' . $this->_for_stack . ' += ((' . $_args['start'] . ' < ' . $_args['stop'] . ') ? ' . $_args['step'] . ' : -' . $_args['step'] . ')):?>';
				if(isset($_args['value'])){
					$_result .= '<?php $_variables[' . $_args['value'] . '] = $for' . $this->_for_stack . ';?>';
				}
				return $_result;
			case '/for':
				$this->_for_stack--;
				return "<?php endfor; ?>";
			case 'switch':
				$from = $this->parseValue($arguments);
				array_push($this->_switch_stack,array("matched"=>false,"var"=>$from));
				return '';
			case '/switch':
				array_pop($this->_switch_stack);
				return '<?php break; endswitch;?>';
			case 'case':
				if(count($this->_switch_stack) > 0){
					$_result = '<?php ';
					$value = $this->parseValue($arguments);
					$_index = count($this->_switch_stack) - 1;
					if(!$this->_switch_stack[$_index]["matched"]){
						$_result .= 'switch(' . $this->_switch_stack[$_index]["var"] . '): ';
						$this->_switch_stack[$_index]["matched"] = true;
					}else{
						$_result .= 'break; ';
					}
					if(!empty($value)){
						$_result .= 'case ' . $value . ': ';
					}else{
						$_result .= 'default: ';
					}
					return $_result . '?>';
				}else{
					trigger_error("Cmspad Template Compiler Error: Unexpected 'case', 'case' can only be in a 'switch'",E_USER_ERROR);
				}
				break;
			case 'assign':
				$_args = $this->compileTagArguments($arguments,array('expr'),array('value'));
				if(!isset($_args['name'])){
					trigger_error("Cmspad Template Compiler Error: Missing 'name' attribute in 'assign' tag",E_USER_ERROR);
				}else{
					if(isset($_args['value'])){
						if(is_numeric($_args['value'])){
							return '<?php $this->addVariable(' . $_args['name'] . ', ' . floatval($_args['value']) . ');?>';
						}else{
							return '<?php $this->addVariable(' . $_args['name'] . ', \'' . $_args['value'] . '\');?>';
						}
					}else if(isset($_args['expr'])){
						return '<?php $this->addVariable(' . $_args['name'] . ', ' . $_args['expr'] . ');?>';
					}else{
						trigger_error("Cmspad Template Compiler Error: Missing 'value' or 'expr' attribute in 'set' tag.",E_USER_ERROR);
					}
				}
				break;
			case 'include':
				$_args = $this->compileTagArguments($arguments);
				if(!isset($_args['name'])){
					trigger_error("Cmspad Template Compiler Error: Missing 'name' attribute in 'include' tag",E_USER_ERROR);
				}else{
					if(isset($_args['portlet'])){
						return '<?php echo cmspad_portlet_template('.$_args['portlet'].','.$_args['name'].');?>';
					}else{
						return '<?php echo cmspad_template('.$_args['name'].');?>';
					}
				}
				break;
			default:
				if(($functionName = $this->engine->getTagFunction($function))){
					$_args = $this->compileTagArguments($arguments, array('exp0','exp1','exp2','exp3','exp4','exp5','exp6','exp7','exp8','exp9'));
					foreach($_args as $key=>$value){
						if(is_bool($value)){
							$value = $value?'true':'false';
						}
						if(is_null($value)){
							$value = 'null';
						}
						$_args[$key] = "'$key' => $value";
					}
					if(is_array($functionName)){
						$functionObjectExpr = '$_functions[\'tag\'][\'' . $function . '\'][0]';
						$functionNameExpr = $functionName[1];
						return '<?php echo ' . $functionObjectExpr . '->' . $functionNameExpr . '(array(' . implode(',',(array)$_args) . '), $this);?>';
					}else{
						return '<?php echo ' . $functionName . '(array(' . implode(',',(array)$_args) . '), $this);?>';
					}
				}else{
					trigger_error('Cmspad Template Compiler Error: Function ' . $function . ' does not exist',E_USER_ERROR);
					return '';
				}
				break;
		}
		return '';
	}
	
	/**
	 * @param string $arguments
	 * @return array
	 */
	public function compileTagArguments($arguments,$exprValues = array(),$staticValues = array()){
		$_match = array();
		$_result = array();
		/*
		 Parse state:
		 0 - expecting attribute name
		 1 - expecting '='
		 2 - expecting attribute value (not '=')
		 */
		$state = 0;
		if(preg_match_all('/(?:' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+))+|[=]/x',$arguments,$_match)){
			foreach($_match[0] as $value){
				switch($state){
					case 0:
						// valid attribute name
						if(is_string($value)){
							$a_name = $value;
							$state = 1;
						}else{
							trigger_error("Cmspad Template Compiler Error: Invalid attribute name: '$value'",E_USER_ERROR);
						}
						break;
					case 1:
						if($value == '='){
							$state = 2;
						}else{
							trigger_error("Cmspad Template Compiler Error: Expecting '=' after '$last_value'",E_USER_ERROR);
						}
						break;
					case 2:
						if($value != '='){
							if($value == 'yes' || $value == 'on' || $value == 'true'){
								$_result[$a_name] = $value = true;
							}elseif($value == 'no' || $value == 'off' || $value == 'false'){
								$_result[$a_name] = $value = false;
							}elseif($value == 'null'){
								$_result[$a_name] = $value = null;
							}elseif(is_numeric($value)){
								$_result[$a_name] = $value = floatval($value);
							}else{
								$value = trim($value,'"');
								if(is_numeric($value)){
									$_result[$a_name] = $value = floatval($value);
								}else{
									if(in_array($a_name,$exprValues)){
										$value = $this->parseValue(trim($value,'\''),true);
									}elseif(in_array($a_name,$staticValues)){
										$value = $this->parseValue(addslashes($value));
									}else{
										$value = $this->parseValue($value);
									}
									$_result[$a_name] = $value;
								}
							}
							$state = 0;
						}else{
							trigger_error("Cmspad Template Compiler Error: '=' cannot be an attribute value",E_USER_ERROR);
						}
						break;
				}
				$last_value = $value;
			}
		}
		if($state != 0){
			if($state == 1){
				trigger_error("Cmspad Template Compiler Error: Expecting '=' after attribute name '$last_value'",E_USER_ERROR);
			}else{
				trigger_error("Cmspad Template Compiler Error: Missing attribute value",E_USER_ERROR);
			}
		}
		return $_result;
	}
	
	private function trimValue($value,$expr = false){
		if(is_numeric($value)){
			return floatval($value);
		}
		
		//:20080625: 修改了赋值为"."导致编译错误的bug.
		$value = trim($value);
		$value = preg_replace('/[^\\\\]*\'\\.(.*?)\\.\'/', '$1', $value);
		//if(substr($value,0,2) == '\'.' && substr($value,2,1) != '\''){
		//	$value = substr($value,2);
		//}
		//if(substr($value,-2) == '.\'' && substr($value,-3,1) != '\''){
		//	$value = substr($value,0,strlen($value) - 2);
		//}
		//:~
		
		if(!is_numeric($value)){
			$fcond = preg_match('/^([a-zA-Z0-9_]+)\((.*)\)/',$value);
			$bcond = preg_match('/([a-zA-Z0-9_]+)\((.*)\)$/',$value);
			$lcond = preg_match('/^\$([a-zA-Z0-9_\[\]\']+)/',$value);
			$rcond = preg_match('/\$([a-zA-Z0-9_\[\]\']+)$/',$value);
			$lstrs = substr($value,0,1) == '\'';
			$rstrs = substr($value,-1) == '\'';
			if(!$fcond && !$lcond && !$lstrs && !$bcond && !$rcond && !$rstrs){
				if($expr){
					if(substr($value, 0, 1) == '.'){
						$value = '\''.$value;
					}
					if(substr($value, -1, 1) == '.'){
						$value .= '\'';
					}
				}else{
					$value = '\'' . $value . '\'';
				}
			}else{
				if(!$expr){
					$valuec = str_replace('\\\'','',$value);
					$siquotc = substr_count($valuec,'\'');
					if($siquotc > 0 && $siquotc % 2 != 0){
						if(substr($valuec,0,2) != '$_' && substr($valuec,0,15) != 'call_user_func('){
							$cp1 = strrpos($valuec,'\'.$');
							$cp2 = strrpos($valuec,'\'.call_user_func(');
							if($cp1 && $cp2){
								$lp1 = $cp1 < $cp2?$cp1:$cp2;
							}else if($cp1){
								$lp1 = $cp1;
							}else if($cp2){
								$lp1 = $cp2;
							}else{
								$lp1 = false;
							}
							$lq1 = strpos($valuec,'\'');
							// '...'.
							if($lp1 && $lq1 <= $lp1){
								$value = '\'' . $value;
							}
						}
						$lc = substr($valuec,-1);
						if($lc != ']' && $lc != ')'){
							$cp1 = strrpos($valuec,'].\'');
							$cp2 = strrpos($valuec,').\'');
							if($cp1 && $cp2){
								$lp2 = $cp1 > $cp2?$cp1:$cp2;
							}else if($cp1){
								$lp2 = $cp1;
							}else if($cp2){
								$lp2 = $cp2;
							}else{
								$lp2 = false;
							}
							$lq2 = strrpos($valuec,'\'');
							// .'...'
							if($lp2 && $lq2 > $lp2){
								$value .= '\'';
							}
						}
					}
				}else{
					// Expression: $page-1
					// Wrong Value: $_variables['page'].'-1
					// Right Value: $_variables['page']-1
					$value = str_replace('].\'',']',$value);
				}
			}
		}
		return $value;
	}
	
	private function parseComparator($cmp){
		switch($cmp){
			case '=':
				$cmp = '==';
				break;
			case '<>':
				$cmp = '!=';
				break;
			case '<':
			case '>':
			case '<=':
			case '>=':
			case '==':
			case '===':
			case '!=':
			case '!==':
				break;
			default:
				trigger_error('Cmspad Template Compiler Error: Unknown comparator modifier: ' . $cmp, E_USER_ERROR);
				return false;
		}
		return $cmp;
	}
	
	private function parseCondition($cond){
		if(substr($cond,0,1) == '"'){
			$cond = substr($cond,1);
		}
		if(substr($cond,-1) == '"'){
			$cond = substr($cond,0,strlen($cond) - 1);
		}
		if(is_numeric($cond)){
			return $cond;
		}else{
			return $this->parseValue($cond);
		}
	}
	
	/**
	 * Convert argument array to string.
	 *
	 * @param array|string $arguments
	 * @return string
	 */
	private function parseValue($arguments,$expr = false){
		if(!$arguments){
			return '';
		}
		if(is_array($arguments)){
			$funcArguments = array();
			for($i = 0,$l = count($arguments);$i < $l;$i++){
				$funcArgument = $this->trimValue($arguments[$i],$expr);
				if(strlen($funcArgument) == 0){
					continue;
				}
				if(is_numeric($funcArgument)){
					$funcArguments[] = $funcArgument;
				}else{
					$funcArguments[] = $this->trimValue($this->compileSysFunctions($funcArgument),$expr);
				}
			}
			return implode(',',$funcArguments);
		}else{
			$arguments = $this->trimValue($arguments,$expr);
			if(is_numeric($arguments)){
				$funcArguments = $arguments;
			}else{
				$funcArguments = $this->compileSysFunctions($arguments,$expr);
			}
			return $this->trimValue($funcArguments,$expr);
		}
	}
	
	/**
	 * Splits Template-Style Variable Names into an Array-Name/Key-Name Components
	 * {example}               :  array( "_variables",                   "example" )  ->  $_variables['example']
	 * {example.value}         :  array( "_variables['example']",        "value" )    ->  $_variables['example']['value']
	 * {example.0.value}       :  array( "_variables['example'][0]",     "value" )    ->  $_variables['example'][0]['value']
	 * {top.example}           :  array( "_stack[0]",              "example" )  ->  $_stack[0]['example']
	 * {parent.example}        :  array( "_stack[$_stack_cnt-1]",  "example" )  ->  $_stack[$_stack_cnt-1]['example']
	 * {parent.parent.example} :  array( "_stack[$_stack_cnt-2]",  "example" )  ->  $_stack[$_stack_cnt-2]['example']
	 *
	 * @param string $tag Variale Name used in Template
	 * @return array  Array Name, Key Name
	 * @access private
	 * @desc Splits Template-Style Variable Names into an Array-Name/Key-Name Components
	 */
	private function parseVariable($tag){
		$parent_level = 0;
		while(substr($tag,0,7) == 'parent.'){
			$tag = substr($tag,7);
			$parent_level++;
		}
		if(substr($tag,0,4) == 'top.'){
			$obj = '_stack[0]';
			$tag = substr($tag,4);
		}elseif($parent_level){
			$obj = '_stack[$_stack_cnt-' . $parent_level . ']';
		}else{
			$obj = '_variables';
		}
		while(is_int(strpos($tag,'.'))){
			list($parent,$tag) = explode('.',$tag,2);
			if(is_numeric($parent)){
				$obj .= "[" . $parent . "]";
			}else{
				$obj .= "['" . $parent . "']";
			}
		}
		$ret = array($obj,$tag);
		return $ret;
	}
	
	/**
	 * @return int Number of subtemplate included
	 * @access private
	 * @desc Count number of subtemplates included in current template
	 */
	private function countIncludeTags($page){
		preg_match_all('/<{include\s+["]?([a-zA-Z0-9_.\-,]+)["]?\s*}>/',$page,$tvar);
		$count_subtemplates = count($tvar[1]);
		$ret = intval($count_subtemplates);
		return $ret;
	}
	
	private function stime(){
		list($usec,$sec) = explode(" ",microtime());
		return ((float)$usec + (float)$sec);
	}
}
?>